package com.yy.hi.library.restful

import android.text.TextUtils
import com.yy.hi.library.hilog.HiLog
import com.yy.hi.library.restful.annotation.BaseUrl
import com.yy.hi.library.restful.annotation.Field
import com.yy.hi.library.restful.annotation.GET
import com.yy.hi.library.restful.annotation.Headers
import com.yy.hi.library.restful.annotation.POST
import com.yy.hi.library.restful.annotation.Path
import java.lang.Exception
import java.lang.IllegalArgumentException
import java.lang.reflect.Method
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type

class MethodParse(val baseUrl: String, method: Method, args: Array<Any>) {

    private var domainUrl: String? = null
    private var formPost: Boolean = true
    private var httpMethod: Int = 0
    private var relativeUrl: String? = null
    private var returnType: Type? = null
    private var headers: MutableMap<String, String> = mutableMapOf()
    private var parameters: MutableMap<String, Any> = mutableMapOf()

    init {
        parseMethodAnnotation(method)

        parseMethodParameters(method, args)

        parseMethodReturnType(method)
    }

    private fun parseMethodReturnType(method: Method) {
        if (method.returnType != HiCall::class) {
            throw IllegalArgumentException("method ${method.name} must be type of HiCall.Class")
        }
        val genericReturnType = method.genericReturnType
        if (genericReturnType is ParameterizedType) {
            val actualTypeArguments = genericReturnType.actualTypeArguments
            require(actualTypeArguments.size == 1) {
                "method ${method.name} can only has one " +
                    "generic return type"
            }
            returnType = actualTypeArguments[0]
        } else {
            throw IllegalArgumentException("method ${method.name} must be have one generic return" +
                " type")
        }
    }

    private fun parseMethodParameters(method: Method, args: Array<Any>) {
        val parameterAnnotations = method.parameterAnnotations
        val equal = parameterAnnotations.size == args.size
        require(equal) {
            "arguments must be have generic"
        }
        for (index in args.indices) {
            val annotations = parameterAnnotations[index]
            require(annotations.size <= 1) {
                "filed can only has one annotation:index = $index"
            }
            val value = args[index]
            require(isPrimitive(value)) {
                "only support 8 basic types index $index"
            }
            val annotation = annotations[0]
            if (annotation is Field) {
                val key = annotation.value
                val value = args[index]
                parameters[key] = value
            } else if (annotation is Path) {
                val replaceName = annotation.value
                val replacement = value.toString()
                if (!TextUtils.isEmpty(replaceName)) {
                    relativeUrl = relativeUrl?.replace("{$replaceName}", replacement)
                }
            } else {
                throw IllegalArgumentException(
                    "cant deal parameters annotation ${annotation.javaClass.toString()}")
            }
        }
    }

    private fun isPrimitive(value: Any): Boolean {
        if (value.javaClass == String::class.java) {
            return true
        }

        try {
            val field = value.javaClass.getField("TYPE")
            val clazz = field[null] as Class<*>
            if (clazz.isPrimitive) {
                return true
            }
        } catch (e: Exception) {
            HiLog.e(e)
        }
        return false
    }

    private fun parseMethodAnnotation(method: Method) {
        val annotations = method.annotations
        annotations.forEach {
            if (it is GET) {
                relativeUrl = it.value
                httpMethod = HiRequest.METHOD.GET
            } else if (it is POST) {
                relativeUrl = it.value
                httpMethod = HiRequest.METHOD.POST
                formPost = it.formPost
            } else if (it is Headers) {
                val headers = it.value
                for (header in headers) {
                    val colon = header.indexOf(":")
                    check(!(colon == 0 || colon == -1)) {
                        "@headers value must be in the form [name:value],but found ${header}"
                    }
                    val name = header.substring(0, colon)
                    val value = header.substring(colon + 1).trim()
                    this.headers[name] = value
                }
            } else if (it is BaseUrl) {
                domainUrl = it.value
            } else {
                throw IllegalArgumentException(
                    "cannot deal method annotation ${it.javaClass.toString()}")
            }
        }
        require(!(httpMethod != HiRequest.METHOD.POST) && !(httpMethod != HiRequest.METHOD.GET)) {
            "method ${method.name} must be have post or get"
        }
        if (domainUrl == null) {
            domainUrl = baseUrl
        }
    }

    fun newRequest(): HiRequest {
        val hiRequest = HiRequest()
        hiRequest.domainUrl = domainUrl
        hiRequest.returnType = returnType
        hiRequest.relativeUrl = relativeUrl
        hiRequest.header = headers
        hiRequest.parameters = parameters
        hiRequest.httpMethod = httpMethod
        return hiRequest
    }

    companion object {
        fun parse(baseUrl: String, method: Method, args: Array<Any>): MethodParse {
            return MethodParse(baseUrl, method, args)
        }
    }
}